using System;
using CoreEx.Common.Cache;

namespace CoreEx.Common.Implementation.Cache
{
    /// <summary>
    /// Represents a cached object that is lazily initialized
    /// </summary>
    /// <typeparam name="TValue">The type of object that this cache represents.</typeparam>
    public class LazyCachedValue<TValue> :ILazyCachedValue<TValue>
    {
        private TValue _value;
        private DateTime _lastAccessed;
        private bool _isInvalidated;
        private static readonly object _syncObject = new object();

        /// <summary>
        /// Returns the cached value.
        /// </summary>
        /// <param name="initializer">The <see cref="Func{TResult}"/> delegate that is executed if the current value is not valid.</param>
        /// <returns><typeparamref name="TValue"/></returns>
        public TValue GetValue(Func<TValue> initializer)
        {
            lock (_syncObject)
            {
                if (ShouldExecuteInitializer())
                    _value = initializer();
                _lastAccessed = DateTime.Now;                
                return _value;
            }
        }        

        /// <summary>
        /// Gets or sets a <see cref="TimeSpan"/> value that indicates the life span of the cached object.
        /// </summary>
        /// <value></value>
        public TimeSpan LifeSpan { get; set;}

        /// <summary>
        /// Invalidates the cache and causes the initializer delegate
        /// passed to the <see cref="GetValue"/> method to execute.
        /// </summary>
        public void Invalidate()
        {
            _isInvalidated = true;
        }

        /// <summary>
        /// Returns a <see cref="bool"/> value that indicates if the initializer should execute.
        /// </summary>
        /// <returns></returns>
        private bool ShouldExecuteInitializer()
        {
            if (Equals(default(TValue), _value))
                return true;
            if (_isInvalidated)
                return true;
            if (DateTime.Now.Subtract(_lastAccessed) > LifeSpan)
                return true;
            return false;
        }
    }
}